//
//  IMChatInputBoxView.swift
//  QingLiuSwift
//
//  Created by xyanl on 2024/9/24.
//  Copyright © 2024 考拉🐨. All rights reserved.
//

import UIKit
import SnapKit
import JKSwiftExtension
import AudioToolbox

/// 整个 bar 的最小高度，即文字只有 1 行时候的高度
let xInputBoxViewMinHeight: CGFloat = 58

enum IMChatInputBoxViewItemType: Int {
    case none
    case input
    case voice
    case emoticon
    case more
}

protocol IMChatInputBoxViewDelegate: AnyObject {
    
    /// 输入框的高度发生了改变（因为输入了值）
    /// - Parameters:
    ///   - inputBox: inputBox description
    ///   - height: 变化的高度
    ///   - isSendText: true：输入内容，文本框变高。false：其他的改变使得高度变化
    func inputBox(inputBox: IMChatInputBoxView, updateHeight height: CGFloat, isSendText: Bool)
    
    /// 点击了系统键盘的发送按钮
    /// - Parameters:
    ///   - inputBox: inputBox description
    ///   - inputNormalText: "害~你好啊[微笑]"
    func inputBox(inputBox: IMChatInputBoxView, onKeyboardSendClick inputNormalText: String)
    
    /// 点击输入框附近的 item
    /// - Parameter itemType: 类型，录音、表情、更多
    func inputBoxDidSeleteItem(itemType: IMChatInputBoxViewItemType, sender: UIButton)
    
    /// 开始录音
    func inputBoxStartRecording(isSuccess: Bool)
    /// 录音结束
    /// - Parameters:
    ///   - inputBox: inputBox description
    ///   - recordFileUrl: 文件路径
    ///   - duration: 录音时长
    func inputBoxEndRecording(recordFinish recordFileUrl: String, duration: Int)
}

// 默认的输入bar，包含了：左侧的语音切换按钮，中间的textview和按住录音按钮，右侧的表情和拓展按钮，（总之，仿微信的输入条）
// 上述按钮都可以设置隐藏，如果还是无法满足你的需求，请自定义UIView，参考TextFieldViewController
class IMChatInputBoxView: UIView {
    
    // 输入栏最小高度即文字只有1行时候的整条Bar的高度）
    private let kInputBarMinHeight: CGFloat = 58
    // Bar里面的YYTextView的最小高度（即文字只有1行时候的YYTextView高度）
    private let kTextViewMinHeight: CGFloat = 42
    // Bar里面的YYTextView的最大高度（即文字有超多行时候的高度）
    private let kTextViewMaxHeight: CGFloat = 147
    /// 按钮的宽高
    fileprivate let xItemWidth: CGFloat = 40
    // 工具栏
    private let kToolBarHeight: CGFloat = 50
    // 间隔
    private let kMargin: CGFloat = 8
    // 工具栏中每一个item的宽高
    private let kItemWidth: CGFloat = 36
    
    /// 记录旧的textView Height
    private var previousTextViewContentHeight: CGFloat = 0
    /// 刚才清空文本框是因为点击了”发送“按钮。加入这个全局变量是为了 Delegate 的 heightDidChange 方法可以回调特殊的返回值
    private var clearInputTextBySendSoon: Bool = false
    
    /// 输入栏TextView的高度发生变化的动画时长（秒）default is 0.2
    private var inputBarHeightChangeAnimationDuration: TimeInterval = 0.2
    /// 点击“发送”按钮时候会清空输入栏，进而导致TextView的高度变化的动画时长（秒）default is 0.1
    private var inputBarHeightChangeAnimationWhenSendDuration: TimeInterval = 0.1
    
    //在切换语音和文本消息的时候，需要保存原本已经输入的文本
    private var inputedText: String = ""
    
    
    /** 录音 **/
    // 时长间隔
    private let kTimerDuration: TimeInterval = 0.5
    // 最长录音时长
    private let kMaxRecordDuration: TimeInterval = 20
    // 剩余多少秒开始倒计时
    private let kRemainCountingDuration: TimeInterval = 10
    
    private var timer: Timer?
    private var duration: TimeInterval = 0
    
    
    weak var delegate: IMChatInputBoxViewDelegate?
    
    private lazy var markView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor(hex: "#242736")
        addSubview(view)
        return view
    }()
    
    /// 文本输入框
    lazy var inputTextView: UITextView = {
        let textView: UITextView = UITextView(frame: .zero)
        textView.font = .systemFont(ofSize: 16)
        textView.returnKeyType = .send
        textView.scrollsToTop = false
        textView.textAlignment = .left
        textView.layer.cornerRadius = 6
        textView.tintColor = .themeColor
        textView.textColor = UIColor(hex: "#FFFFFF", 0.8)
        textView.backgroundColor = UIColor(hex: "#FFFFFF", 0.1)
        textView.textContainerInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8)
        // 如果我设置了left边距，换行的时候，xcode会弹出提示：requesting caretRectForPosition: while the NSTextStorage has oustanding changes . 但是实际运行没任何影响
        // 如果把left（第2、4个参数）设置为0，就不会有警告。原因不明
        // UITextView 内部判断 send 按钮是否可以用
        textView.enablesReturnKeyAutomatically = true
        
        textView.autoresizingMask = .flexibleWidth
        textView.delegate = self
        markView.addSubview(textView)
        return textView
    }()
    
    lazy var sendButton: UIButton = {
        let button = UIButton(type: .custom)
        button.addTarget(self, action: #selector(sendMessageAction), for: .touchUpInside)
        button.setImage(ZegoUIIconSet.iconMessageSend, for: .normal)
        button.setImage(ZegoUIIconSet.iconMessageSendDisable, for: .disabled)
        markView.addSubview(button)
        return button
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        initUI()
        
        // 监听键盘，将要出现
        NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardWillShowNotification), name: UIResponder.keyboardWillShowNotification, object: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func initUI() {
        backgroundColor = UIColor(hex: "#242736")
        
        let lineView = UIView()
        lineView.backgroundColor = UIColor(hex: "#DFDFDF")
        lineView.isHidden = true
        addSubview(lineView)
        lineView.snp.makeConstraints { make in
            make.left.right.top.equalToSuperview()
            make.height.equalTo(0)
        }
        
        markView.snp.makeConstraints { make in
            make.left.right.equalToSuperview()
            make.top.equalTo(lineView.snp.bottom)
            make.height.equalTo(kInputBarMinHeight)
        }
        
        inputTextView.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(kMargin)
            make.top.equalToSuperview().offset(kMargin)
            make.bottom.equalToSuperview().offset(-kMargin)
            make.right.equalToSuperview().offset(-kMargin*2 - kTextViewMinHeight)
        }
        
        sendButton.snp.makeConstraints { make in
            make.right.equalToSuperview().offset(-kMargin)
            make.bottom.equalTo(inputTextView.snp.bottom)
            make.width.height.equalTo(kTextViewMinHeight)
        }
        
        previousTextViewContentHeight = getTextViewContentHeight()
        
        // 用kvo来监听输入文本的改变，进而改变tv高度和整个bar高度
        inputTextView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        endEditing(true)
    }
}

// MARK: - 响应事件
extension IMChatInputBoxView {
    
    /// 点击了发送按钮
    @objc func sendMessageAction() {
        xLog(inputTextView.text)
        delegate?.inputBox(inputBox: self, onKeyboardSendClick: inputTextView.text!)
    }
    
    @objc func onKeyboardWillShowNotification() {
        // 弹出键盘，取消所有 item 的选中状态
        cancelAllItemSelectedState()
    }
    
    // 取消工具栏中所有 item 的选中状态
    func cancelAllItemSelectedState() {
    }
    
    
    /// 设置 textView 焦点状态
    /// - Parameter isFirstResponder: true：获取，false：失去
    func setTextViewFirstResponder(_ isFirstResponder: Bool) {
        if isFirstResponder {
            // 让textView获取焦点
            if !inputTextView.isFirstResponder {
                inputTextView.becomeFirstResponder()
            }
        } else {
            // 让textView失去焦点
            if inputTextView.isFirstResponder {
                inputTextView.resignFirstResponder()
            }
        }
    }
    
    /// 重置输入框的高度
    /// - Parameter changeValue: textView 需要更改的高度, 变化的高度
    func resetTextViewHeightBy(changeValue: CGFloat) {
        let height = markView.frame.size.height
        markView.snp.updateConstraints { make in
            make.height.equalTo(height + changeValue)
        }
    }
    
    
    /// 清空所有
    func clearAll() {
        clearInputTextBySend()
        cancelAllItemSelectedState()
    }
    
    
    // 清除输入的文本，不建议你自己用 inputTextView.text = nil 来情况文本。因为那样的话如果输入栏的文字>1行，你调用 tableView.reload 再 scrollToBottom 会出现 tableView 滚动不流畅
    // 之所以不流畅是因为 tableView 的 scrollToBottom 动画和 onWholeInputViewHeightDidChange 里的动画同时被调用
    // @return 0：当前inputText只有一行；非0：动画时长
    @discardableResult
    func clearInputTextBySend() -> TimeInterval {
        // true:当前inputText只有一行；false：大于一行
        let currentIsOneLine: Bool = inputTextView.frame.size.height == kTextViewMinHeight
        
        // 临时改成true，等回调了delegate要立刻设 false
        clearInputTextBySendSoon = true
        
        // 清空输入栏，如果当前输入栏里的文字行数>1会立即触发KVO
        inputTextView.text = ""
        
        //等走完KVO和KVO里的的delegate，再关掉
        clearInputTextBySendSoon = false
        
        return currentIsOneLine ? 0 : inputBarHeightChangeAnimationWhenSendDuration
    }
    
    
    /// 获取某个YYTextView对象的content高度,判断最小高度 kTextViewMinHeight
    func getTextViewContentHeight() -> CGFloat {
        let height: CGFloat = CGFloat(ceilf(Float(inputTextView.sizeThatFits(inputTextView.frame.size).height)))
        return max(height, kTextViewMinHeight)
    }
    
}



extension IMChatInputBoxView {
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        
        if keyPath == "contentSize" {
            //当输入的文本发生<换行>的时候会进入这里，这是ios系统判断的换行，<换行>包括新增文本导致的换行，也包括删除文本
            
            let textView = object as! UITextView//YYTextView
            // textView 最新高度
            let newContentHeight: CGFloat = getTextViewContentHeight()
            
            // YES表示textView行数减少了
            let isHeightShouldShrink = newContentHeight < previousTextViewContentHeight
            
            // 由于内容的输入，textView应该发生高度的变化，这个值就是应该tv改变的高度差
            var textViewHeightShouldChangeValue: CGFloat = newContentHeight - previousTextViewContentHeight
            
            // xLog("textViewHeightShouldChangeValue 1 \(textViewHeightShouldChangeValue)")
            // 要根据max和minheight重新计算一下高度变化差
            if !isHeightShouldShrink &&
                (previousTextViewContentHeight == kTextViewMaxHeight || textView.text.count == 0) {
                textViewHeightShouldChangeValue = 0
                
            } else {
                textViewHeightShouldChangeValue = min(textViewHeightShouldChangeValue, kTextViewMaxHeight - previousTextViewContentHeight)
            }
            
            // xLog("textViewHeightShouldChangeValue 2 \(textViewHeightShouldChangeValue)")
            
            if textViewHeightShouldChangeValue != 0 {
                // textView的高度有所改变
                UIView.animate(withDuration: clearInputTextBySendSoon ? inputBarHeightChangeAnimationWhenSendDuration : inputBarHeightChangeAnimationDuration) { [self] in
                    if isHeightShouldShrink {
                        // 如果需要缩放, 先改变tv的高度，再修改bar的高度
                        resetTextViewHeightBy(changeValue: textViewHeightShouldChangeValue)
                    }
                    // 设置本bar的frame
                    let inputViewFrame = frame
                    frame = CGRect(x: inputViewFrame.origin.x, y: inputViewFrame.origin.y - textViewHeightShouldChangeValue, width: inputViewFrame.size.width, height: inputViewFrame.size.height + textViewHeightShouldChangeValue)
                    
                    if !isHeightShouldShrink {
                        // 为了兼容低版本ios系统，所以这句代码，不可以跟上面的合并
                        resetTextViewHeightBy(changeValue: textViewHeightShouldChangeValue)
                    }
                    
                    // 告知父视图重新绘制
                    layoutIfNeeded()
                    // 回调给QKeyboardManager
                    delegate?.inputBox(inputBox: self, updateHeight: textViewHeightShouldChangeValue, isSendText: clearInputTextBySendSoon)
                }
                
                
                previousTextViewContentHeight = min(newContentHeight, kTextViewMaxHeight)
            }
            // 这一句可以不写，为了保险还是写了
            clearInputTextBySendSoon = false
            
            // 达到最大高度的时候（无论textView的高度是否有所改变），要更新tv的ContentOffset，让他滚起来
            if previousTextViewContentHeight == kTextViewMaxHeight {
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
                    xLog("刷新结束")
                    let bottomOffset: CGPoint = CGPoint(x: 0, y: newContentHeight - textView.bounds.size.height)
                    textView.setContentOffset(bottomOffset, animated: true)
                }
            }
        }
        
    }
    
}

// MARK: - UITextViewDelegate
extension IMChatInputBoxView: UITextViewDelegate {
    
    func textViewDidChange(_ textView: UITextView) {
        if textView.text.count > 0 {
            sendButton.isEnabled = true
        } else {
            sendButton.isEnabled = false
        }
    }
    
    func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
        cancelAllItemSelectedState()
        return true
    }
    
    func textViewDidBeginEditing(_ textView: UITextView) {
        if previousTextViewContentHeight == 0 {
            previousTextViewContentHeight = getTextViewContentHeight()
        }
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        // textView.resignFirstResponder()
    }
    
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        xLog("输入 【\(text)】")
        if text == "\n" {
            xLog("发送")
            return false
        }
        
        // 当输入@符号的时候, 并且有@好友功能的时候, 触发点击@好友
        if text == "@" {
            // xLog("输入 @")
            // return false
        }
        
        return true
    }
    
}
